#include "CaseDB/Impl/ConnectionImpl.hpp"
#include "CaseDB/Impl/DirectionConversions.hpp"
#include "CaseDB/Impl/ValueConversions.hpp"
#include "CaseDB/Statement.hpp"
#include "CaseDB/RecordSet.hpp"
#include "CaseDB/DatabaseError.hpp"
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlDriver>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlRecord>
#include <QtSql/QSqlField>
#include <cassert>

namespace CaseDB { namespace Impl
{


// ---------------------------------------------------------------------------------------------------------------------

ConnectionImpl::~ConnectionImpl()
{
	database_.close();
	QSqlDatabase::removeDatabase( database_.connectionName());
}

// ---------------------------------------------------------------------------------------------------------------------

ConnectionImpl::ConnectionImpl( const std::string& name, const std::string& type )
	: database_( QSqlDatabase::addDatabase( QString::fromStdString( type), QString::fromStdString( name)))
{
}

// ---------------------------------------------------------------------------------------------------------------------

void ConnectionImpl::open()
{
	if( !database_.open())
	{
		throw DatabaseError( database_.lastError().text().toUtf8().data());
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void ConnectionImpl::close()
{
	database_.close();
}

// ---------------------------------------------------------------------------------------------------------------------

void ConnectionImpl::execute( Statement& statement, QSqlQuery& qtStat)
{
	qtStat.prepare( QString::fromStdString( statement.statement()));
	Statement::ParamIterator curParamI = statement.paramsBegin(),
		endParamI = statement.paramsEnd();
	while( curParamI != endParamI)
	{
		qtStat.addBindValue( 
			ValueConversions::anyToQVariant( curParamI->type(), curParamI->value()), 
			DirectionConversions::toQt( curParamI->direction()));
		++curParamI;
	}
	if( !qtStat.exec())
	{
		QSqlError error = database_.lastError();
		throw DatabaseError( error.text().toUtf8().data());
	}
	// Update the output params
	curParamI = statement.paramsBegin();
	int index = 0;
	while( curParamI != endParamI)
	{
		if( curParamI->direction() == ParamDirectionsEnum::Out || curParamI->direction() == ParamDirectionsEnum::InOut)
		{
			QVariant boundValue = qtStat.boundValue( index);
			curParamI->setValue( ValueConversions::QVariantToAny( curParamI->type(), boundValue));
		}
		++curParamI;
		++index;
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void ConnectionImpl::execute( Statement& statement )
{
	QSqlQuery qtStat( database_);
	this->execute( statement, qtStat);
}

// ---------------------------------------------------------------------------------------------------------------------

void ConnectionImpl::execute( Statement& statement, RecordSet& results )
{
	QSqlQuery qtStat( database_);
	this->execute( statement, qtStat);

	// Now, iterate through the records
	while( qtStat.next())
	{
		QSqlRecord r = qtStat.record();
		assert( static_cast< std::size_t>( r.count()) == results.metadata().size());
		// For each field, set the data in the record
		int fieldCount = r.count();
		RecordSet::Record curRec( results.metadata().size());
		while( fieldCount > 0)
		{
			--fieldCount;
			QSqlField field = r.field( fieldCount);
			QVariant fieldValue = field.isNull() ? QVariant() : field.value();
			curRec[fieldCount] = ValueConversions::QVariantToAny( results.metadata()[ fieldCount], fieldValue);
		}
		results.appendRecord( curRec);
	}
}

} } // namespace Impl // namespace CaseDB
